from collections import OrderedDict
from typing import List, Dict, Set, Tuple, Optional, Union, Any, Callable
import json
from dataclasses import dataclass
from enum import Enum

from numexpr.expressions import double


from entry.DataGenerator import DataGenerator, MoveType
from entry.Job import Job, Operation, ScheduleAssignment, GCLEAN_TYPES
from entry.WaferCassette import WaferCassette,Task
from entry.Machine import Machine,MachinesResults
from entry.Constant import Parameter
from entry.Constant import Parameter
from main import file_logger


class PermFlexFlowShop:
    '''可排序 的 柔性 流水车间'''

    def __init__(self, job: Job = None, task: Task = None):
        # 基础属性
        self.job_type: str = ""
        self.job_operations: List[Operation] = []
        self.wafer_cassettes: List[WaferCassette] = [WaferCassette() for _ in range(3)]
        self.schedule_assignments: List[ScheduleAssignment] = []
        self.machines: List[Machine] = []
        self.machines_maps: Dict[str, Machine] = {}    # {机器名：machine}
        self.job_array: List[List[str]] = [[f"{i + 1}.{j + 1}" for j in range(25)] for i in range(3)]    # 工件数组：3*25
        self.job_sequence_list: List[str] = []   # 工件顺序列表
        self.job_clean_sequence_list: List[str] = []
        self.machines_results: List[MachinesResults] = []

        self.makespan: float = Parameter.INFINITY         # 完工时间：初始为无穷大
        self.makespan_best: float = Parameter.INFINITY    # 最优完工时间：初始为无穷大

        self.schedule_assignments_best: List[ScheduleAssignment] = []
        self.job_schedule_assignments_map: Dict[str, List[ScheduleAssignment]] = {}      # 工件调度结果：{工件名：调度结果}
        self.machine_schedule_assignments_map: Dict[str, List[ScheduleAssignment]] = {}  # 机器调度结果：{机器名：调度结果}

        # 初始化
        if job and task:
            self.job_type = job.name
            self.job_operations = job.operations if job.operations else []

            # 处理晶圆盒
            if task.waferCassettes and len(task.waferCassettes) == 3:
                self.wafer_cassettes = task.waferCassettes

            # 处理机器
            for machine in task.machines:
                self.machines.append(machine)
                self.machines_maps[machine.name] = machine

    def sequencing(self) -> int:
        """确定工件顺序：按照晶圆盒编号"""

        for i in range(len(self.job_array)):
            for j in range(len(self.job_array[i])):
                if self.job_array[i][j] != "0":
                    self.job_sequence_list.append(self.job_array[i][j])

        return 1


    def sequence_insert_job_cleans(self, job_sequence_list: List[str], clean_vecs: List[str], clean_insert_between_nums: List[int]) -> List[str]:
        """在工件加工序列中插入清洗操作"""

        job_clean_sequence_list: List[str] = []
        count = 0   # 统计已处理的工件数量

        # 处理输入参数不一致的情况
        if len(clean_vecs) != len(clean_insert_between_nums):
            raise ValueError("清洗操作列表和插入间隔列表长度必须一致")

        # 遍历原始工件序列
        for job in job_sequence_list:
            count += 1
            job_clean_sequence_list.append(job)

            # 检查是否需要插入清洗操作
            for i in range(len(clean_insert_between_nums)):
                # 插入间隔：如间隔=2，那么当前工件序号是2的倍数时，需要添加清洁操作
                insert_interval = clean_insert_between_nums[i]
                if insert_interval <= 0:
                    raise ValueError(f"插入间隔必须为正整数，当前值: {insert_interval}")

                if count % insert_interval == 0:
                    job_clean_sequence_list.append(clean_vecs[i])

        return job_clean_sequence_list

    def machineInit(self) -> int:
        """初始化前置机器：每个任务都必须的前置加工步骤"""

        # LP：装卸位  TM1：转移模块1  AL：校准模块
        machine_names = ["LP", "TM1", "AL"]
        for name in machine_names:
            machine = Machine()
            machine.set_name(name)
            self.machines.append(machine)
            self.machines_maps[name] = machine
        return 1

    def operationInit(self, operations: List[Operation]) -> int:
        """初始化完整工序：前置工序 + 加工工序 + 后置工序"""


        """
        前置工序：晶圆从装卸位LP运送到LL真空锁，开始加工
        """

        '''初始工序：装卸位取晶圆'''
        op1 = Operation()
        op1.machine_names.append("LP")
        op1.processing_times.append(1.0)
        op1.move_types.append(MoveType.PREPARE_MOVE.value)   #LP开阀门
        op1.move_times.append(1.0)
        operations.append(op1)

        '''运输工序：LP to AL by TM1'''
        op2 = Operation()
        op2.machine_names.append("TM1")
        op2.processing_times.append(Parameter.TAKE_TIME_TM1 + 1.0 + Parameter.PUT_TIME_TM1)
        op2.move_types.append(MoveType.PICK_MOVE.value)   #TM1 从LP取晶圆
        op2.move_times.append(Parameter.TAKE_TIME_TM1)
        op2.move_types.append(MoveType.TRANS_MOVE.value)  #TM1运晶圆到AL
        op2.move_times.append(1.0)
        op2.move_types.append(MoveType.PLACE_MOVE.value)  #TM1 放晶圆
        op2.move_times.append(Parameter.PUT_TIME_TM1)
        operations.append(op2)

        '''校准工序：AL校准8s'''
        op3 = Operation()
        op3.machine_names.append("AL")
        op3.processing_times.append(8.0)
        op3.move_types.append(MoveType.ALIGN_MOVE.value)  # 校准操作
        op3.move_times.append(8.0)
        operations.append(op3)

        '''运输工序：AL to LLA/LLB by TM1'''
        op4 = Operation()
        op4.machine_names.append("TM1")
        op4.processing_times.append(Parameter.TAKE_TIME_TM1 + 1.0 + Parameter.PUT_TIME_TM1)
        op4.move_types.append(MoveType.PICK_MOVE.value)
        op4.move_times.append(Parameter.TAKE_TIME_TM1)
        op4.move_types.append(MoveType.TRANS_MOVE.value)
        op4.move_times.append(1.0)
        op4.move_types.append(MoveType.PLACE_MOVE.value)
        op4.move_times.append(Parameter.PUT_TIME_TM1)
        operations.append(op4)

        for op in self.job_operations:
            operations.append(op)   # 上述前置准备工序后 添加工件正式加工工序


        """
        后置工序：晶圆加工完毕，从LL运送回装卸位LP
        """

        '''运输工序：LL to LP by TM1'''
        op5 = Operation()
        op5.machine_names.append("TM1")
        op5.processing_times.append(Parameter.TAKE_TIME_TM1 + 1.0 + Parameter.PUT_TIME_TM1)
        op5.move_types.append(MoveType.PICK_MOVE.value)
        op5.move_times.append(Parameter.TAKE_TIME_TM1)
        op5.move_types.append(MoveType.TRANS_MOVE.value)
        op5.move_times.append(1.0)
        op5.move_types.append(MoveType.PLACE_MOVE.value)
        op5.move_times.append(Parameter.PUT_TIME_TM1)
        operations.append(op5)

        # 返回1，表示初始化成功
        return 1


    def execute_scheduling(self):
        # 确定工件出片顺序
        self.sequencing()
        clean_vecs: List[str] = []  # 清洁操作类型
        clean_insert_between_nums: List[int] = [0]   # 清洁间隔
        operations: List[Operation] = []   # 工序列表

        # 初始化前置工序机器
        self.machineInit()
        # 初始化工序列表
        self.operationInit(operations)
        schedule_results_record: List[ScheduleAssignment] = []  #调度结果记录
        clean_vecs.append(GCLEAN_TYPES[0])   # 空闲型清洁

        # 累计加工数量阈值
        for i in range(int(Parameter.CLEAN_ACCUMULATED_PROCESSED_ITEM_NUM)):
            # TODO 依次尝试不同的清洁间隔，1-13
            file_logger.info("间隔清洁个数：{}",i)
            clean_insert_between_nums[0] = i
            # 工件序列中加入清洁
            jobCleanSequenceList = self.sequence_insert_job_cleans(self.job_sequence_list, clean_vecs, clean_insert_between_nums)
            self.schedule_assignments.clear()
            # TODO 开始执行 算法部分

        # 多个调度结果（不同清洁间隔）中找最优解
        if not self.machines_results:
            print("没有有效数据")
        else:
            min_result = min((res for res in self.machines_results if res.makespan != INFINITY),
                             key=lambda x: x.makespan, default=None)

            if min_result is None:
                print("未找到最优解")
            else:
                print(f"Minmum makespan: {min_result.makespan}")

            # 调度结果，写入json文件
            # TODO writeJSON()
            return 1




    def is_overlap(self, time_slot1: Tuple[float, float], time_slot2: Tuple[float, float]) -> bool:
        """检查两个时间区间是否重叠"""

        start1, end1 = time_slot1
        start2, end2 = time_slot2
        # 返回true：表示有重叠
        return start1 < end2 and start2 < end1

    def record_schedule_assignment(
            self,
            move_id: int,
            move_type: int,
            module_name: str,
            mat_id: str,
            start_time: float,
            end_time: float,
            slot_id: int
    ) -> ScheduleAssignment:
        """创建并记录一个工序的调度结果"""

        # 检查时间有效性
        if end_time<start_time:
            raise ValueError(f"结束时间：{end_time}， 不能早于开始时间：{start_time}")

        # 创建一个工序的调度分配
        assignment = ScheduleAssignment()
        assignment.move_id = move_id    # 工件id
        assignment.mat_id = mat_id      # 物料编号
        assignment.move_type = move_type    # move类型
        assignment.module_name = module_name   # 模块名称
        assignment.start_time = start_time     # move开始时间
        assignment.end_time = end_time         # move结束时间
        assignment.slot_id = slot_id           # 槽位id


        return assignment

    def write_json(self, file_name: str, schedule_results: List[ScheduleAssignment]) -> int:
        """将调度结果保存为JSON文件"""

        # 创建有序字典
        json_data = OrderedDict()
        json_data["MoveList"] = []

        # 遍历调度结果，转为有序字典
        for schedule in schedule_results:
            move_item = OrderedDict([
                ("StartTime", schedule.start_time),
                ("EndTime", schedule.end_time),
                ("MoveID", schedule.move_id),
                # TODO：move_type 枚举类
                ("MoveType", schedule.move_type),
                ("ModuleName", schedule.module_name),
                ("MatID", schedule.mat_id),
                ("SlotID", schedule.slot_id)
            ])
            json_data["MoveList"].append(move_item)

        try:
            with open(file_name, "w", encoding="utf-8") as f:
                json.dump(json_data, f, ensure_ascii=False, indent=4)
            return 1
        except Exception as e:
            print(f"无法打开文件：{file_name}，错误信息：{e}")
            return 0

    def time_tabling(self,sequences: List[str],
                     operations: List[Operation],
                     makespan: float,
                     scheduleResultsRecord: List[ScheduleAssignment]) -> int:
        """
        柔性作业调度车间：
        用于根据工序序列和机器信息进行调度并生成调度结果 时间表
        :param sequences: 插入清洁操作的工件处理序列
        :param operations: 工序列表
        :param makespan: 输出参数，最大完工时间
        :param scheduleResultsRecord: 输出参数，调度结果记录
        :return: 1表示成功，0表示失败
        """

        # 初始变量
        # 工序列表，第一层是邻域分配方法，第二层是只保留一个机器的operation序列
        lns_operations: List[List[Operation]] = []
        schedule_result: List[ScheduleAssignment] = []  # 临时调度结果
        machine_map: Dict[str, Machine] = {}  # 机器字典
        machines_result = MachinesResults()

        # 深拷贝初始机器状态
        for name, machine in self.machines_maps.items():
            new_machine = Machine(name=name)
            new_machine.productTimes = [*machine.productTimes]  # 深拷贝加工时间
            machine_map[name] = new_machine

        # 多机器工序拆分成单机器工序：只保留一个可用机器的operation序列
        for op in operations:
            lns_operation = []  # lns_operations的内层列表：单机器序列
            # 遍历每个工序可用机器
            for idx in range(len(op.machine_names)):
                # 单机器工序
                new_op = Operation()
                new_op.id = op.id
                new_op.machine_name = op.machine_names[idx]
                new_op.processing_times = op.processing_times[idx]

                lns_operation.append(new_op)

            lns_operations.append(lns_operation)

        # 工件序列类型转换
        # sequence = list(sequences)

        # 遍历每个工件列表（含清洁操作）
        for i in range(len(sequences)):
            # 打印日志
            print(f"第{i}个工件的id是{sequences[i]}")
            print(f"序列长度：{len(sequences)}")

            processing_time_sum = 0.0
            machine_Vec_map_temp: List[Dict[str, Machine]] = []  # 临时机器状态表
            makespan_partial: List[float] = []   # 完工时间

            # 处理非清洁类型工件
            if sequences[i] not in GCLEAN_TYPES:
                # 遍历工件i所有工序
                for j in range(len(operations)):
                    # 第1个工件的第1个工序
                    if i==0 and j==0:
                        print(f"第{i}个工件的第{j}个工序")
                        # 第1个工件第1个工序：安排到第一个可用机器上，加工时间从0开始
                        machine = machine_map[operations[j].machine_names[0]]    # 第一台可用机器
                        machine.productTimes.append((0.0,operations[j].processing_times[0]))    # 加工时间段

                        # 调度结果
                        schedule_assignment = ScheduleAssignment()
                        schedule_assignment.move_id = i+j+1;   # move编号：所有工件所有工序编号
                        schedule_assignment.mat_id = sequences[i]
                        schedule_assignment.move_type = MoveType.PREPARE_MOVE.value
                        schedule_assignment.module_name = operations[j].machine_names[0]  # 第一台机器名称
                        schedule_assignment.start_time = machine_map[operations[j].machine_names[0]].productTimes[-1][0]
                        schedule_assignment.end_time = machine_map[operations[j].machine_names[0]].productTimes[-1][1]
                        schedule_assignment.slot_id = 1

                        schedule_result.append(schedule_assignment)

                    # 第一个工件的其他工序
                    elif i==0 and j!=0:
                        print(f"第{i}个工件的第{j}个工序")
                        # 当前工序开始全局时间：前一个工序结束时间
                        prev_machine_name = operations[j-1].machine_names[0]
                        cur_start_time = machine_map[prev_machine_name].productTimes[-1][1]

                        # 当前工序机器的加工时间段
                        cur_machine = machine_map[operations[j].machine_names[0]]
                        cur_machine.productTimes.append((cur_start_time,cur_start_time+operations[j].processing_times[0]))

                        # 当前工序中每一步【操作】开始执行的时间：从当前工序开始执行为起点
                        timeSum = 0.0
                        # 遍历工序的多个操作：如取+运+放
                        for mv_idx in range(len(operations[j].move_types)):
                            mv_type = operations[j].move_types[mv_idx]  # 当前操作类型
                            mv_time = operations[j].move_times[mv_idx]  # 当前操作的执行时间（工序中的每一个操作）
                            schedule_assignment = ScheduleAssignment()
                            schedule_assignment.move_id = i + j + mv_type + 1
                            schedule_assignment.mat_id = sequences[i]
                            schedule_assignment.move_type = operations[j].move_types[mv_idx]
                            schedule_assignment.module_name = operations[j].machine_names[0]
                            schedule_assignment.start_time = cur_start_time + timeSum    # 当前操作全局开始时间：工序开始时间+操作开始时间
                            schedule_assignment.end_time = cur_start_time + timeSum + mv_time  # 当前操作全局结束时间：工序开始时间 + 操作开始时间 + 操作执行时间
                            schedule_assignment.slot_id = 1

                            schedule_result.append(schedule_assignment)
                            timeSum += mv_time      # 当前移动类型的时间

                    # 其他工件工序：非第一个工件
                    else:
                        print(f"第{i}个工件的第{j}个工序")

                        for m in range(len(operations[j].machine_names)):
                            cur_machine = machine_map[operations[j].machine_names[m]]
                            machines_map_temp: Dict[str, Machine] = {}
                            # 当前机器的生产时间不为空：已经有工序用过
                            if cur_machine.productTimes:
                                # 复制当前机器状态
                                machine_temp = Machine(name=cur_machine.name)
                                machine_temp.productTimes = [*cur_machine.productTimes]

                                # 安排当前工序时间：当前工序安排到改机器上，挨着改机器前面的工序放
                                start = machine_temp.productTimes[-1][1]   # 开始时间：当前机器上一次执行加工的结束时间
                                end = start + operations[j].processing_times[m]   # 结束时间： 开始时间+ 当前操作执行时间
                                machine_temp.productTimes.append((start, end))
                                machines_map_temp[operations[j].machine_names[m]] = machine_temp

                                # 剩下工序根据当前工序时间窗口反推，如果冲突，说明当前时间窗口不合适
                                is_overlap = False
                                time_sum = 0.0      # 前序工序k 至当前工序的累计加工时间
                                # 向前检查工序( j-1 到 1 )
                                for k in range(j-1, 0, -1):
                                    # 计算前序工序时间窗口
                                    end2 = machine_temp.productTimes[-1][0] - time_sum      # 结束时间：当前工序j的开始时间 - k至当前工序的累计加工时间
                                    start2 = end2 - operations[k].processing_times[0]       # 开始时间：k的结束时间-工序k的自身加工时间

                                    if start2 < 0 or end2 < 0:
                                        is_overlap = True
                                        break

                                    time_sum += operations[k].processing_times[0]
                                    # 前序工序时间窗口
                                    time_slot1 = (start2, end2)
                                    # 遍历前序工序的所有可用机器l：判断倒推的前序工序时间窗口是否与前序工序的机器已有执行时间冲突
                                    for l in range(len(operations[k].machine_names)):
                                        # 冲突机器数量：
                                        feasible_counter = 0

                                        # 遍历前序工序加工机器l 的 所有加工时间
                                        for prod_time in machine_map[operations[k].machine_names[l]].productTimes:
                                            # 检查两个时间区间是否重叠
                                            if self.is_overlap(time_slot1, prod_time):
                                                # 重叠：说明与机器l有冲突
                                                feasible_counter += 1
                                                break

                                        # 前序工序时间窗口与所有可用的机器l都冲突：说明该时间窗口不合适
                                        if feasible_counter == len(operations[k].machine_names):
                                            is_overlap = True
                                            break
                                        # 记录可行机器状态：前序工序k倒推的时间窗口 安排在机器l上可行
                                        else:
                                            # TODO：为啥源代码要循环记录？
                                            new_machine = Machine(name=operations[k].machine_names[l])
                                            new_machine.productTimes.append((start2,end2))
                                            machines_map_temp[operations[k].machine_names[l]] = new_machine

                                    # 前序工序与当前工序安排的时间窗口冲突
                                    if is_overlap:
                                        break

                                # 将当前工序j安排在当前机器m上导致前序工序冲突：跳过当前机器
                                if is_overlap:
                                    continue

                                # 将当前工序j安排在当前机器m上可行：向后检查
                                time_sum = 0.0      # 现在记录的是当前工序结束 到后续工序开始的累计时间
                                for k in range(j + 1, len(operations)):
                                    # 计算后续工序时间窗口
                                    start_time = machines_map_temp[operations[j].machine_names[m]].productTimes[-1][1] + time_sum
                                    end_time = start_time + operations[k].processing_times[0]
                                    time_sum += operations[k].processing_times[0]
                                    # 后续工序的加工时间窗口
                                    time_slot1 = (start_time, end_time)

                                    # 检查后续工序的所有可选机器是否冲突
                                    # 后续工序安排在机器l上加工
                                    for l in range(len(operations[k].machine_names)):
                                        # 记录冲突的机器数量
                                        feasible_counter = 0

                                        # 检查与已有加工时间重叠
                                        for prod_time in machine_map[operations[k].machine_names[l]].productTimes:
                                            # 与机器l已有时间窗口冲突，该后续工序不能安排在机器l上
                                            if self.is_overlap(time_slot1, prod_time):
                                                feasible_counter += 1
                                                break

                                        # 后续工序时间窗口与 工序所有可用机器都冲突：说明当前时间窗口不可用
                                        if(feasible_counter == len(operations[k].machine_names)):
                                            is_overlap = True
                                            break
                                        else:
                                            # TODO：为啥不在else里记录机器状态
                                            # 记录机器：后续工序时间窗口与l无冲突，可以安排在该机器上
                                            new_machine = Machine(name=operations[k].machine_names[l])
                                            new_machine.productTimes.append(time_slot1)
                                            machines_map_temp[operations[k].machine_names[l]] = new_machine

                                    # 有重叠：后续工序与所有可用机器都冲突
                                    if is_overlap:
                                        break
                                    if k == len(operations)-1 :
                                        # 遍历到最后一个工序，记录最后一个工序结束时间
                                        makespan_partial.append(end_time)

                                # 将当前工序j安排在当前机器m上导致后续工序冲突：跳过当前机器
                                if is_overlap:
                                    continue

                                # 能到这里，说明当前工序可以安排在机器m上，没有冲突
                                # TODO：记录该工序相邻的makespan：最后一个工序的开始时间和解？？
                                machine_Vec_map_temp.append(machines_map_temp)
                            else:
                                # TODO：当前机器m生产时间为空，不处理？？
                                print("当前机器m生产时间为空")

                    print("log：准备进入剩下的工作")

            # 执行空闲清洁
            elif sequences[i] == GCLEAN_TYPES[0]:
                # 清洁工序
                print(f"处理清洁工序: {sequences[i]}")
                for machine in machine_map.values():
                    if machine.productTimes:
                        # 安排清洁时间
                        last_end = machine.productTimes[-1][1]   # 该机器最后一个工序的结束时间
                        start = last_end + Parameter.CLEAN_FREE_WAIT_TIME   # 清洁开始时间：工序结束时间 + 空闲时间
                        end = start + Parameter.CLEAN_FREE_EXECUTION_TIME   # 清洁结束时间：清洁开始时间 + 清洁执行时间
                        # 该机器的执行时间段中插入清洁时间段
                        machine.productTimes.append((start, end))

            # 选择最优调度方案：记录的临时makespan中找一个最小的，作为最终解
            if len(makespan_partial)>0 and i!=0 and sequences[i] != GCLEAN_TYPES[0]:
                # 最小完工时间的索引
                min_idx = makespan_partial.index(min(makespan_partial))
                # 最优调度方案用到的机器列表
                selected_machines = machine_Vec_map_temp[min_idx]

                # 更新机器状态和调度结果
                for j in range(len(operations)):
                    for m in range(len(operations[j].machine_names)):
                        # 最优调度方案 包含 当前机器
                        machine_name = operations[j].machine_names[m]
                        if machine_name in selected_machines:
                            # 更新机器加工时间
                            machine_map[machine_name].productTimes = [
                                *selected_machines[machine_name].productTimes
                            ]

                            # 记录工序里的每一个操作
                            time_sum = 0.0   # 当前工序开始时间至 当前操作开始时间的间隔
                            for op_mv_idx in range(len(operations[j].move_types)):
                                mv_type = operations[j].move_types[op_mv_idx]
                                mv_time = operations[j].move_types[op_mv_idx]
                                # 最后一次加工的开始时间
                                last_start = selected_machines[machine_name].productTimes[-1][0]

                                schedule_assignment = self.record_schedule_assignment(
                                    move_id= j+m,
                                    # TODO：移动编号为啥是j+m?  移动类型不应该是move_type[mv]
                                    move_type=operations[j].move_types[m],
                                    module_name= operations[j].machine_names[m],
                                    mat_id= sequences[i],
                                    start_time= last_start + time_sum,
                                    end_time= last_start + time_sum + mv_time,
                                    slot_id= 1
                                )
                                schedule_result.append(schedule_assignment)
                                time_sum += mv_time
                            break  # 只处理第一个可行机器
            elif len(makespan_partial)==0 and i!=0 and sequences[i] != GCLEAN_TYPES[1]:
                # 无可行方案
                return 0

        # 整理最终结果
        for schedule in schedule_result:
            scheduleResultsRecord.append(schedule)

        machines_result.schedule_results = scheduleResultsRecord
        machines_result.makespan = schedule_result[-1].end_time
        machines_result.machinesMap = machine_map

        makespan = machines_result.makespan
        self.machines_results.append(machines_result)

        return 1





















